#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

const int basesize = 1024;
const int argvnum = 64;//最多一次性接受64条命令
const int envnum = 64;//环境变量最多64

//全局的命令行参数
char *gargv[argvnum];
int gargc = 0;

//全局的环境变量
char *genv[envnum];

//全局的shell路径
char pwd[basesize];//当前路径
char pwdenv[basesize];//环境变量中的路径

int lastcode = 0;//返回值，判断错误类型


//获取用户名
string GetUserName()
{
    string name = getenv("USER");

    //考虑用户名为空的情况
    return  name.empty() ? "None":name;
}

//获取主机令
string GetHostName()
{
    string hostname = getenv("HOSTNAME");

    return hostname.empty() ? "None" : hostname;
}

//获取当前路径
string GetPwd()
{

    if(nullptr == getcwd(pwd,sizeof(pwd))) 
        return nullptr;

    snprintf(pwdenv,sizeof(pwdenv),"PWD=%s",pwd);
    putenv(pwdenv);

    return pwd;


    //这种写法会导致myshell打印的命令行提示符不会随路径的改而改变
    //因为这是从环境变量本身获取的
    //应该是自己维护自己的环境变量，命令行提示符随环境变量的改变而改变
   // string pwd = getenv("PWD");
   // return pwd.empty() ? "None" : pwd;
}


//命令行中的路径只显示最后一部分
string LastDir()
{
    string curr = GetPwd();

    if(curr == "/" || curr == "None") 
        return curr;

    size_t pos = curr.rfind("/");
    if(pos == std::string::npos)
        return curr;

    return curr.substr(pos+1);
}


//命令行的输出格式
string MakeCommandLine()
{
    char command_line[basesize];
    snprintf(command_line,basesize,"[%s@%s %s]*",\
             GetUserName().c_str(),GetHostName().c_str(),LastDir().c_str());

    return command_line;
}


//1.命令行提示符:在环境变量中获取
void  PrintCommand()
{
    printf("%s",MakeCommandLine().c_str());
    fflush(stdout);
}


//2.获取用户命令
bool  GetCommand(char command_buffer[],int size)
{
    //将输入的命令当作一个完整的字符
    //"ls -a -l -s"
    char *result = fgets(command_buffer,size,stdin);
    if(!result)
    {
        return false;
    }

    command_buffer[strlen(command_buffer)-1 ]= 0;//消除最后enter键的影响
    if(strlen(command_buffer) == 0)
        return false;

    return true;

}

//3.分析命令
//"ls -a -l -n" 转换为 "ls" "-a" "-l" "-n"
void ParseCommandline(char command_buffer[],int len)
{
    (void)len;
    
    //每次使用全局变量，都要进行一次清空
    memset(gargv,0,sizeof(gargv));
    gargc = 0;

    const char *sep = " ";
    gargv[gargc++] = strtok(command_buffer,sep);
    while((bool)(gargv[gargc++] = strtok(nullptr,sep)));

    gargc--;
}



//4.执行命令
bool ExecuteCommand()
{
    //让子进程执行
    pid_t id = fork();
    if(id < 0)
        return false;

    if(id == 0)
    {
        //1.执行命令
        execvpe(gargv[0],gargv,genv);
        //2.退出
        exit(1);
    }

    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(rid > 0)
    {
        if(WIFEXITED(status))
        {
            lastcode = WEXITSTATUS(status);
        }
        else
        {
            lastcode = 100;
        }
        return true;
    }

    return false;
}


//插入新的环境变量
void AddEnv(const char* item)
{
    int index = 0;
    while(genv[index])
    {
        index++;
    }

    genv[index] = (char*)malloc(strlen(item)+1);
    strncpy(genv[index],item,strlen(item)+1);
    genv[++index] = nullptr;

}



//在shell中
//有些命令必须由子进程执行
//而有些命令不能由子进程执行，要由shell自己执行 --- 内建命令
bool CheckBuiltCommand()
{
    if(strcmp(gargv[0],"cd") == 0)
    {
        //内建命令
        if(gargc == 2)
        {
            //cd .. / cd /
            chdir(gargv[1]);
            lastcode = 0;
        }

        return true;
    }

    else if(strcmp(gargv[0],"export") == 0)
    {
        //export也是内建命令，导入环境变量
        if(gargc == 2)
        {
            AddEnv(gargv[1]);
            lastcode = 0;
        }
        else
        {
            lastcode = 2;
        }
    
        return true;
    }

    else if(strcmp(gargv[0],"env") == 0)
    {
        for(int i = 0 ;genv[i];i++)
        {
            printf("%s\n",genv[i]);
        }
        lastcode = 0;

        return true;
    }
    else if(strcmp(gargv[0],"echo") == 0)
    {
        if(gargc == 2)
        {
            //echo $? 
            //echo $PATH
            //echo hello
            if(gargv[1][0] == '$')
            {
                if(gargv[1][1] == '?')
                {
                    printf("%d\n",lastcode);
                    lastcode = 0;
                }
            }
            else
            {
                printf("%s\n",gargv[1]);
                lastcode = 0;
            }
        }

        else
        {
            lastcode = 3;
        }
        return true;

    }


    return false;

}


///配置shell的环境变量，从系统环境中获取
void InitEnv()
{
    extern char **environ;
    int index = 0;//下标
    while(environ[index])
    {
        genv[index] = (char*)malloc(strlen(environ[index])+1);
        strncpy(genv[index],environ[index],strlen(environ[index]+1));
        index++;
    }

    //环境变量参数表中以nullprt结尾
    genv[index] = nullptr;

}

void Debug()
{
    printf("argv : %d\n",gargc);
    for(int i = 0; gargv[i];i++)
    {
        printf("argv[%d]:%s\n",i, gargv[i]);
    }
}

int main()
{
    InitEnv();
    char command_buffer[basesize];

    //shell只要自己不主动退出，是一直运行不停的
    while(true)
    {

        //1.命令行提示符：用户名，主机，地址
        PrintCommand();
        
        //测试
        //printf("\n");
        //sleep(1);


        //2.获取用户命令
        if(!GetCommand(command_buffer,basesize))
        {
            continue;
        }
        //printf("%s\n",command_buffer);


        //3.分析命令
         ParseCommandline(command_buffer,strlen(command_buffer));
         //Debug();


        //4执行命令
        if(CheckBuiltCommand())
        {
            continue;
        }

       ExecuteCommand();
    }


    return 0;
}

